OSC 8 Hyperlink Support - #207
Conversation
Implemented OSC 8 hyperlink support in the integrated client: - Extended RawAnsi struct to include url and urlId fields. - Enhanced ANSI parser to recognize and parse OSC 8 sequences (including BEL and ST terminators). - Updated DisplayWidget to handle hyperlink rendering via QTextCharFormat anchors. - Implemented support for Mudlet-compatible URI schemes: - send: Executes the command immediately (appends \n). - prompt: Pre-fills the input widget with the command. - Added synchronized hover underlining for fragments sharing the same URL ID using setExtraSelections. - Integrated security check for file:// URIs to ensure they only open local files. - Updated AnsiTokenizer to correctly skip OSC sequences. - Added unit tests in TestGlobal for OSC 8 parsing.
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
Reviewer's GuideAdds OSC 8 hyperlink parsing and rendering to the ANSI pipeline, threads URL/URL-id through RawAnsi into QText formatting, and wires the display widget to handle Mudlet-style hyperlink schemes with synchronized hover underlining and basic file:// security checks, while generalizing the ANSI regex/tokenizer and dropping constexpr from RawAnsi due to QString usage. Sequence diagram for OSC 8 parsing and hyperlink formatting pipelinesequenceDiagram
participant TelnetStream
participant AnsiTokenizer_Iterator as AnsiTokenizerIterator
participant AnsiTextUtils
participant AnsiColorParser
participant RawAnsiState as RawAnsi
participant AnsiTextHelper
participant QTextFormat as QTextCharFormat
TelnetStream->>AnsiTokenizer_Iterator: next escape sequence
AnsiTokenizer_Iterator->>AnsiTextUtils: weakAnsiRegex match
AnsiTokenizer_Iterator->>AnsiTextUtils: parseAnsiColor(before, ansiStr)
alt OSC8 hyperlink
AnsiTextUtils->>AnsiTextUtils: isOsc8(ansiStr)
AnsiTextUtils->>AnsiTextUtils: parseOsc8(next, ansiStr)
AnsiTextUtils-->>AnsiTokenizer_Iterator: RawAnsi next
else non hyperlink ANSI color
AnsiTextUtils->>AnsiTextUtils: isAnsiColor(ansiStr)
AnsiTextUtils->>AnsiColorParser: for_each(ansiStr)
AnsiColorParser->>RawAnsiState: update color and style
AnsiTextUtils-->>AnsiTokenizer_Iterator: RawAnsi next
end
AnsiTokenizer_Iterator-->>AnsiTextHelper: ansiStr and RawAnsi currentAnsi
AnsiTextHelper->>AnsiTextUtils: parseAnsiColor(currentAnsi, ansiStr)
AnsiTextUtils-->>AnsiTextHelper: optional RawAnsi updated
alt parsing succeeded
AnsiTextHelper->>QTextFormat: updateFormat(format, defaults, currentAnsi, updated)
QTextFormat->>QTextFormat: setAnchor(updated.url is not empty)
QTextFormat->>QTextFormat: setAnchorHref(updated.url)
QTextFormat->>QTextFormat: setProperty(URL_ID_PROPERTY, updated.urlId)
AnsiTextHelper->>RawAnsiState: currentAnsi = updated
else not ANSI or unsupported
AnsiTextHelper-->>AnsiTokenizer_Iterator: ignore sequence
end
Sequence diagram for hyperlink click handling with Mudlet schemes and security checkssequenceDiagram
actor User
participant DisplayWidget
participant DisplayWidgetOutputs
participant ClientWidget
participant Telnet
participant StackedInputWidget
participant QDesktopServices
participant QHostInfo
User->>DisplayWidget: Click hyperlink
DisplayWidget-->>DisplayWidget: emit anchorClicked(QUrl url)
DisplayWidget->>DisplayWidget: lambda anchorClicked handler
DisplayWidget->>DisplayWidget: scheme = url.scheme()
alt scheme send
DisplayWidget->>DisplayWidgetOutputs: sendUserInput(url.path() + newline)
DisplayWidgetOutputs->>ClientWidget: virt_sendUserInput(msg)
ClientWidget->>Telnet: sendToMud(msg)
else scheme prompt
DisplayWidget->>DisplayWidgetOutputs: setPrompt(url.path())
DisplayWidgetOutputs->>ClientWidget: virt_setPrompt(msg)
ClientWidget->>StackedInputWidget: setPrompt(msg)
StackedInputWidget->>StackedInputWidget: setPlainText(msg)
StackedInputWidget->>StackedInputWidget: moveCursor(End)
StackedInputWidget->>StackedInputWidget: setFocus()
else scheme file
DisplayWidget->>DisplayWidget: host = url.host()
DisplayWidget->>QHostInfo: localHostName()
QHostInfo-->>DisplayWidget: localName
alt host empty or localhost or localName
DisplayWidget->>QDesktopServices: openUrl(url)
else non local host
DisplayWidget-->>DisplayWidget: log warning and ignore
end
else other schemes
DisplayWidget->>QDesktopServices: openUrl(url)
end
Updated class diagram for ANSI hyperlink and OSC 8 supportclassDiagram
class RawAnsi {
+AnsiColorVariant fg
+AnsiColorVariant bg
+AnsiColorVariant ul
+QString url
+QString urlId
-AnsiStyleFlags m_flags
-AnsiUnderlineStyleEnum m_underlineStyle
+RawAnsi()
+RawAnsi(AnsiStyleFlags flags, AnsiColorVariant fg_, AnsiColorVariant bg_, AnsiColorVariant ul_)
+bool hasForegroundColor()
+bool hasBackgroundColor()
+bool hasUnderlineColor()
+RawAnsi withForeground(AnsiColorVariant var)
+RawAnsi withBackground(AnsiColorVariant var)
+RawAnsi withUnderlineColor(AnsiColorVariant var)
+RawAnsi withUnderlineStyle(AnsiUnderlineStyleEnum style)
+RawAnsi withForeground(AnsiColor16Enum newColor)
+RawAnsi withBackground(AnsiColor16Enum newColor)
+RawAnsi withUnderlineColor(AnsiColor16Enum newColor)
+bool hasUnderline()
+void setUnderline()
+void clearUnderline()
+void setUnderlineStyle(AnsiUnderlineStyleEnum style)
+AnsiStyleFlags getFlags()
+AnsiUnderlineStyleEnum getUnderlineStyle()
+void setFlag(AnsiStyleFlagEnum flag)
+void removeFlag(AnsiStyleFlagEnum flag)
+bool operator==(RawAnsi rhs)
+bool operator!=(RawAnsi rhs)
}
class AnsiTextHelper {
+static int URL_ID_PROPERTY
+QTextEdit &textEdit
+QTextCursor cursor
+QTextCharFormat format
+RawAnsi currentAnsi
+void displayText(QStringView input_str)
}
class DisplayWidgetOutputs {
+void showMessage(QString msg, int timeout)
+void windowSizeChanged(int width, int height)
+void returnFocusToInput()
+void showPreview(bool visible)
+void sendUserInput(QString msg)
+void setPrompt(QString msg)
#virtual void virt_showMessage(QString msg, int timeout)
#virtual void virt_windowSizeChanged(int width, int height)
#virtual void virt_returnFocusToInput()
#virtual void virt_showPreview(bool visible)
#virtual void virt_sendUserInput(QString msg)
#virtual void virt_setPrompt(QString msg)
}
class DisplayWidget {
<<QObject>>
+QString m_lastUrlId
+DisplayWidget(QWidget *parent)
+void slot_displayText(QStringView str)
+void resizeEvent(QResizeEvent *event)
+void keyPressEvent(QKeyEvent *event)
+bool eventFilter(QObject *watched, QEvent *event)
-void updateHoverUnderline(QString urlId)
+signals void anchorClicked(QUrl url)
}
class StackedInputWidget {
<<QObject>>
+void gotMultiLineInput(QString input)
+void gotPasswordInput(QString input)
+void setPrompt(QString msg)
+void setEchoMode(EchoModeEnum echoMode)
+EchoModeEnum getEchoMode()
}
class ClientWidget {
+void initDisplayWidget()
+StackedInputWidget &getInput()
+DisplayWidget &getDisplay()
+Telnet &getTelnet()
+StackedInputWidget &getPreview()
}
class Telnet {
+void sendToMud(QString msg)
}
RawAnsi <.. AnsiTextHelper : uses
AnsiTextHelper <.. DisplayWidget : uses
DisplayWidgetOutputs <.. ClientWidget : implements
DisplayWidget --> DisplayWidgetOutputs : uses
ClientWidget --> StackedInputWidget : owns
ClientWidget --> DisplayWidget : owns
ClientWidget --> Telnet : owns
class AnsiTokenizer {
+class Iterator
}
class AnsiTokenizer_Iterator {
+AnsiTokenizer::Iterator::size_type skip_ansi()
+AnsiStringToken getCurrent()
}
AnsiTokenizer_Iterator --|> AnsiTokenizer
class AnsiColorParser {
+void for_each(QStringView ansi) const
}
RawAnsi <.. AnsiColorParser : updates
class AnsiTextUtils {
+bool isAnsiColor(QStringView ansi)
+bool isAnsiEraseLine(QStringView ansi)
+std::optional~RawAnsi~ parseAnsiColor(RawAnsi before, QStringView ansi)
+bool isOsc8(QStringView ansi)
+void parseOsc8(RawAnsi next, QStringView ansi)
+QRegularExpression weakAnsiRegex
}
AnsiTextUtils ..> RawAnsi : returns
AnsiTokenizer ..> AnsiTextUtils : uses
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The
updateHoverUnderlineimplementation scans every block and fragment on every mouse move, which may become expensive for large scrollback buffers; consider caching fragments byurlIdor limiting the scan to visible blocks only. - Using
setExtraSelectionsto drive hover underlining will overwrite any existing extra selections (e.g., search highlights or other decorations); consider merging with, or layering on top of, other selection sources instead of replacing them outright. - The OSC-aware ANSI regex is now duplicated in both
weakAnsiRegexandAnsiTextHelper::ansi_regex; it may be worth centralizing this pattern (or at least a shared helper) to avoid divergence if the OSC/CSI syntax needs adjustment later.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `updateHoverUnderline` implementation scans every block and fragment on every mouse move, which may become expensive for large scrollback buffers; consider caching fragments by `urlId` or limiting the scan to visible blocks only.
- Using `setExtraSelections` to drive hover underlining will overwrite any existing extra selections (e.g., search highlights or other decorations); consider merging with, or layering on top of, other selection sources instead of replacing them outright.
- The OSC-aware ANSI regex is now duplicated in both `weakAnsiRegex` and `AnsiTextHelper::ansi_regex`; it may be worth centralizing this pattern (or at least a shared helper) to avoid divergence if the OSC/CSI syntax needs adjustment later.
## Individual Comments
### Comment 1
<location path="src/client/displaywidget.cpp" line_range="121-139" />
<code_context>
+ viewport()->installEventFilter(this);
+ viewport()->setMouseTracking(true);
+
+ connect(this, &DisplayWidget::anchorClicked, this, [this](const QUrl &url) {
+ QString scheme = url.scheme();
+ if (scheme == u"send") {
+ getOutput().sendUserInput(url.path() + u"\n");
+ } else if (scheme == u"prompt") {
+ // Pre-fill input widget
+ getOutput().setPrompt(url.path());
+ } else if (scheme == u"file") {
+ // Security check for file:// URIs
+ QString host = url.host();
+ if (host.isEmpty() || host == u"localhost" || host == QHostInfo::localHostName()) {
+ QDesktopServices::openUrl(url);
+ } else {
+ qWarning() << "OSC 8: Ignored file URI with non-local host:" << host;
+ }
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Consider tightening which URL schemes are opened via QDesktopServices to reduce potential misuse of OSC 8 links.
All other schemes are currently passed directly to QDesktopServices::openUrl, including arbitrary or custom ones coming from OSC 8 sequences. To reduce the impact of a malicious or misconfigured server, consider explicitly whitelisting allowed schemes (e.g. http, https, mailto) or blacklisting clearly unsafe ones before opening them.
```suggestion
connect(this, &DisplayWidget::anchorClicked, this, [this](const QUrl &url) {
QString scheme = url.scheme().toLower();
if (scheme == u"send") {
getOutput().sendUserInput(url.path() + u"\n");
} else if (scheme == u"prompt") {
// Pre-fill input widget
getOutput().setPrompt(url.path());
} else if (scheme == u"file") {
// Security check for file:// URIs
QString host = url.host();
if (host.isEmpty() || host == u"localhost" || host == QHostInfo::localHostName()) {
QDesktopServices::openUrl(url);
} else {
qWarning() << "OSC 8: Ignored file URI with non-local host:" << host;
}
} else if (scheme == u"http" || scheme == u"https" || scheme == u"mailto") {
QDesktopServices::openUrl(url);
} else {
qWarning() << "OSC 8: Ignored URL with unsupported scheme:" << scheme << "url:" << url;
}
});
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| connect(this, &DisplayWidget::anchorClicked, this, [this](const QUrl &url) { | ||
| QString scheme = url.scheme(); | ||
| if (scheme == u"send") { | ||
| getOutput().sendUserInput(url.path() + u"\n"); | ||
| } else if (scheme == u"prompt") { | ||
| // Pre-fill input widget | ||
| getOutput().setPrompt(url.path()); | ||
| } else if (scheme == u"file") { | ||
| // Security check for file:// URIs | ||
| QString host = url.host(); | ||
| if (host.isEmpty() || host == u"localhost" || host == QHostInfo::localHostName()) { | ||
| QDesktopServices::openUrl(url); | ||
| } else { | ||
| qWarning() << "OSC 8: Ignored file URI with non-local host:" << host; | ||
| } | ||
| } else { | ||
| QDesktopServices::openUrl(url); | ||
| } | ||
| }); |
There was a problem hiding this comment.
🚨 suggestion (security): Consider tightening which URL schemes are opened via QDesktopServices to reduce potential misuse of OSC 8 links.
All other schemes are currently passed directly to QDesktopServices::openUrl, including arbitrary or custom ones coming from OSC 8 sequences. To reduce the impact of a malicious or misconfigured server, consider explicitly whitelisting allowed schemes (e.g. http, https, mailto) or blacklisting clearly unsafe ones before opening them.
| connect(this, &DisplayWidget::anchorClicked, this, [this](const QUrl &url) { | |
| QString scheme = url.scheme(); | |
| if (scheme == u"send") { | |
| getOutput().sendUserInput(url.path() + u"\n"); | |
| } else if (scheme == u"prompt") { | |
| // Pre-fill input widget | |
| getOutput().setPrompt(url.path()); | |
| } else if (scheme == u"file") { | |
| // Security check for file:// URIs | |
| QString host = url.host(); | |
| if (host.isEmpty() || host == u"localhost" || host == QHostInfo::localHostName()) { | |
| QDesktopServices::openUrl(url); | |
| } else { | |
| qWarning() << "OSC 8: Ignored file URI with non-local host:" << host; | |
| } | |
| } else { | |
| QDesktopServices::openUrl(url); | |
| } | |
| }); | |
| connect(this, &DisplayWidget::anchorClicked, this, [this](const QUrl &url) { | |
| QString scheme = url.scheme().toLower(); | |
| if (scheme == u"send") { | |
| getOutput().sendUserInput(url.path() + u"\n"); | |
| } else if (scheme == u"prompt") { | |
| // Pre-fill input widget | |
| getOutput().setPrompt(url.path()); | |
| } else if (scheme == u"file") { | |
| // Security check for file:// URIs | |
| QString host = url.host(); | |
| if (host.isEmpty() || host == u"localhost" || host == QHostInfo::localHostName()) { | |
| QDesktopServices::openUrl(url); | |
| } else { | |
| qWarning() << "OSC 8: Ignored file URI with non-local host:" << host; | |
| } | |
| } else if (scheme == u"http" || scheme == u"https" || scheme == u"mailto") { | |
| QDesktopServices::openUrl(url); | |
| } else { | |
| qWarning() << "OSC 8: Ignored URL with unsupported scheme:" << scheme << "url:" << url; | |
| } | |
| }); |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #207 +/- ##
==========================================
+ Coverage 25.40% 25.43% +0.02%
==========================================
Files 519 519
Lines 43102 43228 +126
Branches 4698 4720 +22
==========================================
+ Hits 10952 10995 +43
- Misses 32150 32233 +83 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This commit implements support for terminal hyperlinks (OSC 8) in the MMapper client. Key changes include: - Extended `RawAnsi` struct to store `QString url` and `QString urlId`. - Removed `constexpr` from `RawAnsi` and related formatting constants across the codebase (e.g., `Map.cpp`, `World.cpp`, `ChangePrinter.cpp`) as `RawAnsi` is no longer a literal type. - Updated `AnsiTextUtils` to parse OSC 8 sequences with both ST and BEL terminators, including support for the optional `id` parameter. - Enhanced `DisplayWidget` to render links as clickable anchors and implemented handling for `send:`, `prompt:`, and local `file:` schemes. - Implemented synchronized underlining in `DisplayWidget` using a viewport event filter and extra selections to highlight all matching URL IDs on hover. - Added `setPrompt` to `StackedInputWidget` and connected signals in `ClientWidget` to allow display interaction with the input buffer. - Added unit tests for OSC 8 parsing in `TestGlobal`.
This commit implements support for terminal hyperlinks (OSC 8) in the MMapper client, including fixes for clang-format violations identified in CI. Key changes include: - Extended `RawAnsi` struct to store `QString url` and `QString urlId`. - Removed `constexpr` from `RawAnsi` and related formatting constants across the codebase (e.g., `Map.cpp`, `World.cpp`, `ChangePrinter.cpp`) as `RawAnsi` is no longer a literal type. - Updated `AnsiTextUtils` to parse OSC 8 sequences with both ST and BEL terminators, including support for the optional `id` parameter. - Enhanced `DisplayWidget` to render links as clickable anchors and implemented handling for `send:`, `prompt:`, and local `file:` schemes. - Implemented synchronized underlining in `DisplayWidget` using a viewport event filter and extra selections to highlight all matching URL IDs on hover. - Added `setPrompt` to `StackedInputWidget` and connected signals in `ClientWidget` to allow display interaction with the input buffer. - Added unit tests for OSC 8 parsing in `TestGlobal`.
This commit implements support for terminal hyperlinks (OSC 8) in the MMapper client, including fixes for build errors and deprecation warnings identified in CI. Key changes include: - Extended `RawAnsi` struct to store `QString url` and `QString urlId`. - Removed `constexpr` from `RawAnsi` and related constants as `RawAnsi` is no longer a literal type. - Updated `AnsiTextUtils` to parse OSC 8 sequences with both ST and BEL terminators, and fixed a precision loss error (`shorten-64-to-32`). - Fixed `QRegularExpression::match` and `globalMatch` deprecation warnings by using `matchView` and `globalMatchView` for Qt >= 6.6. - Enhanced `DisplayWidget` to render links as clickable anchors and implemented interaction schemes (`send:`, `prompt:`, `file:`). - Implemented synchronized underlining in `DisplayWidget` using a viewport event filter and extra selections. - Added unit tests for OSC 8 parsing in `TestGlobal`. - Ensured all modified files follow `clang-format` rules.
This commit implements support for terminal hyperlinks (OSC 8) in the
MMapper client, while resolving several issues identified in CI:
1. **Precision Loss Fix**: Changed `int` to `qsizetype` for string
indices in `AnsiTextUtils.cpp` to resolve `-Wshorten-64-to-32`.
2. **Qt 6.8 Deprecation Fixes**: Wrapped `QRegularExpression::match` and
`globalMatch` calls with version-guarded `matchView` and
`globalMatchView` for Qt versions 6.6 and newer.
3. **CI Dependency Fix**: Updated AppImage and Test workflows to use
`libqt6svg6-dev` instead of the non-existent `qt6-svg-dev` package.
4. **Code Quality**: Ensured all modified files are compliant with
`clang-format`.
Core feature implementation:
- Extended `RawAnsi` to store URL data.
- Updated ANSI parser to recognize OSC 8 sequences.
- Enhanced `DisplayWidget` to render anchors and handle `send:`,
`prompt:`, and local `file:` schemes.
- Implemented synchronized underlining for matching URL IDs on hover.
- Added unit tests for OSC 8 parsing.
This commit implements support for terminal hyperlinks (OSC 8) in the
MMapper client, while resolving several issues identified in CI:
1. **Precision Loss Fix**: Changed `int` to `qsizetype` for string
indices in `AnsiTextUtils.cpp` to resolve `-Wshorten-64-to-32`.
2. **Qt 6.8 Deprecation Fixes**: Wrapped `QRegularExpression::match` and
`globalMatch` calls with version-guarded `matchView` and
`globalMatchView` for Qt versions 6.6 and newer.
3. **CI Dependency Fix**: Updated AppImage and Test workflows to use
`libqt6svg6-dev` instead of the non-existent `qt6-svg-dev` package.
4. **Code Quality**: Ensured all modified files are compliant with
`clang-format`.
Core feature implementation:
- Extended `RawAnsi` to store URL data.
- Updated ANSI parser to recognize OSC 8 sequences.
- Enhanced `DisplayWidget` to render anchors and handle `send:`,
`prompt:`, and local `file:` schemes.
- Implemented synchronized underlining for matching URL IDs on hover.
- Added unit tests for OSC 8 parsing in `TestGlobal`.
This commit implements support for terminal hyperlinks (OSC 8) in the
MMapper client, while resolving several issues identified in CI:
1. **Precision Loss Fix**: Changed `int` to `qsizetype` for string
indices in `AnsiTextUtils.cpp` to resolve `-Wshorten-64-to-32`.
2. **Qt 6.8 Deprecation Fixes**: Wrapped `QRegularExpression::match` and
`globalMatch` calls with version-guarded `matchView` and
`globalMatchView` for Qt versions 6.6 and newer.
3. **QtGlobal Inclusion**: Added `#include <QtGlobal>` to ensure
`QT_VERSION` is available for version guards.
4. **CI Dependency Fix**: Updated AppImage, Test, and Release workflows
to use `libqt6svg6-dev` instead of the non-existent `qt6-svg-dev`.
5. **CMake Cleanup**: Changed `add_definitions` to
`add_compile_definitions` for `QT_DISABLE_DEPRECATED_UP_TO`.
6. **Code Quality**: Ensured all modified files are compliant with
`clang-format`.
Core feature implementation:
- Extended `RawAnsi` to store URL data.
- Updated ANSI parser to recognize OSC 8 sequences.
- Enhanced `DisplayWidget` to render anchors and handle `send:`,
`prompt:`, and local `file:` schemes.
- Implemented synchronized underlining for matching URL IDs on hover.
- Added unit tests for OSC 8 parsing.
This commit implements support for terminal hyperlinks (OSC 8) in the
MMapper client, while resolving several issues identified in CI:
1. **Precision Loss Fix**: Changed `int` to `qsizetype` for string
indices in `AnsiTextUtils.cpp` to resolve `-Wshorten-64-to-32`.
2. **Qt 6.8 Deprecation Fixes**: Wrapped `QRegularExpression::match` and
`globalMatch` calls with version-guarded `matchView` and
`globalMatchView` for Qt versions 6.6 and newer.
3. **QtGlobal Inclusion**: Added `#include <QtGlobal>` to ensure
`QT_VERSION` is available for version guards.
4. **Safety Fix**: Fixed unsafe `matchView` usage in `UpdateDialog.cpp`
by ensuring the subject `QString` outlives the match object.
5. **CMake Cleanup**: Changed `add_definitions` to
`add_compile_definitions` for `QT_DISABLE_DEPRECATED_UP_TO`.
6. **Code Quality**: Ensured all modified files are compliant with
`clang-format`.
Core feature implementation:
- Extended `RawAnsi` to store URL data.
- Updated ANSI parser to recognize OSC 8 sequences.
- Enhanced `DisplayWidget` to render anchors and handle `send:`,
`prompt:`, and local `file:` schemes.
- Implemented synchronized underlining for matching URL IDs on hover.
- Added unit tests for OSC 8 parsing.
e8139f3 to
c119262
Compare
ae664f2 to
bcd8fca
Compare
This PR adds full support for OSC 8 hyperlinks to the MMapper integrated client.
Key features:
ESC ] 8 ; params ; URI ST). It supports bothESC \andBEL(\x07) as terminators.send:(immediate command execution) andprompt:(input pre-filling) schemes was added, following Mudlet's standard.idparameter (e.g., across line breaks or multiple fragments) are highlighted together when any of them is hovered. This was achieved using a viewport event filter andQTextEdit::ExtraSelection.file://URIs are restricted to the local host to prevent security risks associated with remote file execution.The
RawAnsistruct was updated to store URL data, which required removingconstexprfrom its constructors and some utility functions due to the inclusion ofQString. Existingconstexprusages in the codebase were updated toconst.PR created automatically by Jules for task 17754462110983904247 started by @nschimme
Summary by Sourcery
Add OSC 8 hyperlink handling to the ANSI/OSC parser and integrated client, enabling clickable and synchronized hyperlinks in MUD output with appropriate security checks and UI behavior.
New Features:
Enhancements:
Tests: